#ifndef MA_POINTLESS_ENTITY_COMPONENT_SYSTEM
#define MA_POINTLESS_ENTITY_COMPONENT_SYSTEM

#include <vector>
#include <memory>
#include <numeric>
#include <limits>
#include <string>

#include "simple/support/meta.hpp"
#include "simple/support/tuple_utils.hpp"
#include "simple/support/function_utils.hpp"
#include "simple/support/rational.hpp"
#include "simple/support/algorithm/utils.hpp"
#include "simple/support/vuple.hpp"
#include "simple/geom/vector.hpp"


namespace meta = simple::support::meta;
using simple::support::meta_constant;

template <typename... Interfaces>
class entities;

template <typename Base, typename... Interfaces>
// TODO: enable if is_abstract<Base> && has_virtual_destructor<Base>
// and maybe is_abstract<Interfaces>, but not sure
struct object_interface
{
	static constexpr size_t type_count = 1 + sizeof...(Interfaces);
};

// TODO: parameterise std::unique_ptr and std::vector templates?? do I need that within a project or between projects, cause in a latter case can get away with using declaration in a configuration header
template <typename... Types>
class components
{
	public:

	using types = std::tuple<Types...>;

	template <typename Element, typename ...Args>
	auto& emplace(Args&&... args)
	{
		using simple::support::meta::find_t;
		using simple::support::meta::bind;
		using simple::support::meta::make_index_segment;
		using simple::support::tuple_car;
		using simple::support::tuple_car_t;
		using simple::support::tuple_tie_cdr;
		using simple::support::tie_subtuple;
		using simple::support::is_template_instance_v;

		using found = find_t<bind<is_component, size_constant<0>, Element>, types>;
		static_assert(found::value != std::tuple_size_v<types>, "Element type not found in known component list.");
		if constexpr (is_template_instance_v<object_interface, typename found::type>)
		{
			auto object_vectors = tie_subtuple(vectors,
				make_index_segment<
					found::functor::binding::value,
					found::type::type_count
				>()
			);

			auto& elements = tuple_car(object_vectors);

			auto element = std::make_unique<Element>(std::forward<Args>(args)...);
			Element& raw = *element.get();

			add_interface(raw, tuple_tie_cdr(object_vectors));

			elements.emplace_back(std::move(element)); // assuming raw pointer will not change... that's safe right?
			return raw;
		}
		else
		{
			return std::get<found::functor::binding::value>(vectors).emplace_back(std::forward<Args>(args)...);
		}
	}

	template <typename Element>
	Element& push(Element&& element)
	{ return emplace<std::remove_cvref_t<Element>>(std::forward<Element>(element)); }

	// TODO: return a range ??
	template <typename Component>
	const auto& get() const noexcept
	{
		return std::get<meta::find_v<Component, flat_types>>(vectors);
	}

	std::string log_sizes() const
	{
		std::string info;
		simple::support::transform([&](auto& v){
			info += std::to_string(v.size()) + ": " + typeid(typename std::remove_reference_t<decltype(v)>::value_type).name() + '\n';
		}, vectors);
		return info;
	}

	using size_type = simple::geom::vector<std::size_t, meta::list_size_v<types>>;
	size_type size() const noexcept
	{
		return std::make_from_tuple<size_type>(simple::support::transform(
			[](auto& v) {return v.size();}, vectors ));
	}

	private:

	template <typename T>
	struct flatten_object_interface { using type = std::tuple<T>; };
	template <typename Base, typename... Interfaces>
	struct flatten_object_interface<object_interface<Base, Interfaces...>>
	{ using type = std::tuple<std::unique_ptr<Base>, Interfaces*...>; };

	template <size_t i> using size_constant = std::integral_constant<size_t, i>;

	template <typename flat_index, typename T, typename Component>
	struct is_component : std::is_same<T,Component>
	{
		using binding = size_constant<flat_index{} + !is_component::value>;
	};

	template <typename flat_index, typename T, typename Base, typename... Interfaces>
	struct is_component<flat_index, T, object_interface<Base,Interfaces...>>
	{
		static constexpr bool value = std::is_base_of_v<Base, T>;
		static constexpr size_t increment =
			[](bool value)
			{
				return value
					? 0
					: object_interface<Base,Interfaces...>::type_count
				;
			}
			(is_component::value);
		using binding = size_constant<flat_index{} + increment>;
	};

	using flat_types = meta::reconstruct_t<types,flatten_object_interface>;

	template <typename T>
	using container = std::vector<T>;

	template <typename Tuple>
	using make_containers_t = meta::transform_t<Tuple, container>;

	template <typename T>
	using container_ref = container<T>&;

	template <typename... Ts>
	using make_container_refs_t = meta::transform_t<std::tuple<Ts...>, container_ref>;

	template <typename T>
	using iterator  = typename std::vector<T>::const_iterator;

	using iterators = meta::transform_t<flat_types, iterator>;

	template <typename El>
	void add_interface(El&, std::tuple<>){}

	template <typename El, typename In, typename... Rest>
	// NOTE: this shorthand can't deduce the In :/
	// void add_interface(El& element, make_container_refs_t<In*, Rest...> interfaces)
	void add_interface(El& element, std::tuple<container<In*>&, container<Rest>&...> interfaces)
	{
		using simple::support::tuple_car;
		using simple::support::tuple_tie_cdr;
		if constexpr (std::is_base_of_v<In, El>)
			tuple_car(interfaces).push_back(&element);
		add_interface(element,tuple_tie_cdr(interfaces));
	}

	make_containers_t<flat_types> vectors;

	template <typename Component>
	auto& get() noexcept
	{
		return std::get<meta::find_v<Component, flat_types>>(vectors);
	}


	friend class entities<Types...>;

};

using entity_id_t = simple::support::rational<unsigned,
	  meta_constant<unsigned, 1u << (std::numeric_limits<unsigned>::digits/2)>>;

struct prop_name
{
	// WARNING: werid str, null-padded, don't treat as null-terminated
	std::array<char, 30> private_data;

	constexpr prop_name(const char* name) : private_data()
	{
		auto len = std::string_view(name).size();
		assert(len < private_data.size());
		std::copy(name, name+len, private_data.data());
	}

	constexpr std::string_view to_string() const
	{
		return {private_data.data(), private_data.size()};
	}

	constexpr auto operator <=> (const prop_name&) const = default;
};

template <prop_name Name, typename T>
struct prop
{
	constexpr static const auto name = Name;
	using type = T;
};

template <typename T, std::size_t count>
struct metavector {};

using entity_component_size_t = unsigned short;

template <typename Candidate, typename PropType>
struct meta_prop_count { constexpr static entity_component_size_t value = 0; };

template <typename T>
struct meta_prop_count<T,T> { constexpr static entity_component_size_t value = 1; };

template <typename P, prop_name name, typename F, typename ... R>
struct meta_prop_count<meta::list<prop<name, F>, R...>, P>
{ constexpr static entity_component_size_t value = meta_prop_count<F, P>::value + meta_prop_count<meta::list<R...>, P>::value; };

template <typename C, typename P>
requires ( requires { typename C::prop_list; } )
struct meta_prop_count<C,P>
{ constexpr static entity_component_size_t value = meta_prop_count<typename C::prop_list, P>::value; };

template <typename P, typename C, std::size_t count>
struct meta_prop_count<metavector<C,count>, P>
{ constexpr static entity_component_size_t value = meta_prop_count<C,P>::value * count; };

template <typename C, typename P>
constexpr std::size_t meta_prop_count_v = meta_prop_count<C,P>::value;

template <typename T, typename... Values>
constexpr T from_meta_list_values(meta::list<Values...>)
{
	return T{Values::value...};
}

template <typename... Components>
class entities
{
	public:
	using components_type = components<Components...>;
	using types = meta::concat_t<meta::list<>,typename components_type::flat_types>;
	using entity_size_t = simple::geom::vector<entity_component_size_t, meta::list_size_v<types>>;


	entities() : _components(), offsets() {}
	entities(components_type components) :
		_components(std::move(components)),
		offsets(_components.size())
	{}

	template <typename T>
	constexpr static entity_size_t prop_count_v = from_meta_list_values<entity_size_t>(
		meta::transform_t<types, meta::bind<meta_prop_count, T>::template bound>{});

	template <prop_name name>
	struct prop_name_matcher
	{
		template <typename count, typename Prop>
		struct func
		{
			constexpr static bool value = Prop::name == name;
			using binding = meta_constant<entity_size_t, count::value + (value ? entity_size_t{} : prop_count_v<typename Prop::type>)>;
		};
	};

	template <typename... Props>
	struct metastruct
	{
		using prop_list = meta::list<Props...>;

		constexpr static entity_size_t size = prop_count_v<prop_list>;

		template <typename BaseIt>
		struct proxy
		{
			BaseIt it;

			template <prop_name name>
			constexpr decltype(auto) get()
			{
				using found = meta::find_t<meta::bind<prop_name_matcher<name>::template func, meta_constant<entity_size_t>>, prop_list>;
				static_assert(found::value != meta::list_size_v<prop_list>, "Metastruct: property not found."); // NOTE: wanna say something like, name.to_string() + " is not a property of "s + metastruct.schema_string() but static_assert can't even take a const char*

				const auto offset_it = it +
					simple::support::vuple_from(found::functor::binding::value);
				constexpr auto prop_type_index = meta::find_v<typename found::type::type, types>;
				if constexpr (prop_type_index != meta::list_size_v<types>)
					return *std::get<prop_type_index>(offset_it);
				else
					return offset_it; // decorate in metastruct/vector proxy
			}
		};

		template <typename BaseIt>
		struct iterator
		{
			BaseIt base;
			iterator& operator++()
			{
				base += simple::support::vuple_from(size);
				return *this;
			}

			constexpr proxy<BaseIt> operator*() { return {base}; }
		};
	};

	template <typename Structure>
	class entity_struct
	{
		entities& world;
		const entity_id_t id;

		bool owning;
		public:
		~entity_struct()
		{
			if(owning)
				world.erase(id);
		}
		entity_struct(entity_struct& other) = delete;
		entity_struct& operator=(entity_struct& other) = delete;
		entity_struct& operator=(entity_struct&& other) = delete;
		entity_struct(entity_struct&& other)
			: entity_struct(other.world, other.id)
		{
			other.release();
		}

		entity_struct& release()
		{
			owning = false;
			return *this;
		}

		entity_struct(entities& world) :
			world(world),
			// TODO: id(world.make_structure<Structure>()),
			owning(true)
		{}

		entity_struct(entities& world, const entity_id_t& block_id) :
			world(world),
			// TODO: id(world.make_structure<Structure>(block_id)),
			owning(true)
		{}
	};

	template <typename T>
	struct entity
	{
		public:
		T components;
		const entity_id_t id;
		entities& world;
		bool owning;

		~entity()
		{
			if(owning)
				world.erase(id);
		}

		entity(entity& other) = delete;
		entity& operator=(entity& other) = delete;
		entity& operator=(entity&& other) = delete;
		entity(entity&& other)
			: entity(other.world, std::move(other.components), other.id)
		{
			other.release();
		}

		entity& release()
		{
			owning = false;
			return *this;
		}

		private:
		entity(entities& world, T&& components, const entity_id_t& id) :
			components(std::forward<T>(components)),
			id(id),
			world(world),
			owning(true)
		{}
		friend class entities;
	};

	// TODO; emplace specific components to replace the make function below,
	// component type1, args1
	// component_type2, args2
	// ...
	// component_typeN, argsN
	//
	// maybe use a component helper template, so it looks like this:
	// entities.emplace
	// (
	//   component<Type1>(args1...),
	//   component<Type2>(args2...),
	//   component<Type3>(args3...),
	//   component<Type4>(args4...),
	// );
	// can also sfinae on this helper template.
	//
	// return a predictable entity object with references to all components and the id
	//
	// then again do you actually need to make new types of entity at runtime? entity types (which are defined by entity_sizes) should also be compile time, and also also also maybe fixed per id block
	// and when not, can still be RLE

	auto next_block_id()
	{
		return entity_id_t{} +
			(empty(entity_ids) ? 0 : unsigned(entity_ids.back()+1))
		;
	}

	template <typename Function>
	auto make(Function&& function)
	{
		return make(entity_id_t{} +
			(empty(entity_ids) ? 0 : unsigned(entity_ids.back()))
		, std::forward<Function>(function));
	}

	template <typename Function>
	entity<std::invoke_result_t<
		Function, components<Components...>&>>
	make(entity_id_t block_id, Function&& function)
	{
		using simple::support::transform;
		auto size_before = _components.size();

		auto&& product = std::invoke(
			std::forward<Function>(function),
			_components);

		auto [id, pos] = get_id(block_id);

		auto size_pos = entity_sizes.begin() + (pos - entity_ids.begin());

		if(pos != entity_ids.end())
		{
			auto componets_pos = std::accumulate(entity_sizes.begin(), size_pos, offsets);
			transform([](auto component_pos, auto size_before, auto& component)
			{
				std::rotate( // !
					component.begin() + component_pos,
					component.begin() + size_before,
					component.end()
				);
			}, componets_pos, size_before, _components.vectors);
		}

		entity_sizes.insert(size_pos, _components.size() - size_before);

		entity_ids.insert(pos,id);

		return
		{
			*this,
			std::forward<decltype(product)>(product),
			id
		};
	};

	// TODO: swap for entities of same size, to allow stuff like remove erase

	// template <typename Element, typename ...Args>
	// Element& emplace(Args&&... args)
	// {
	// 	return make([&](auto& components) -> auto&
	// 	{
	// 		return
	// 			components.template emplace<Element>
	// 				(std::forward<Args>(args)...);
	// 	}).components;
	// }

	// TODO: reuse code from find, why is it so hard :/
	// TODO: forget() method, that will turn this into a nop (you know, the classic problem: we technically own all objects we hand out, but we pretend that the user owns them instead with owning handles. now the user's destruction pattern might not be very efficient for our arrangement, and we can do nothing about that, exceeeeept when we know we are going to be destroyed as well, then we can ignore user requests to destroy and take care of it all in one go, unfortunately if the user is following RAII diligently, everything will be destroyed before us, so we have to give them a way to indicate this doomsday scenario manually)

	template <typename Projection>
	void erase(entity_id_t first_id, entity_id_t last_id, Projection proj)
	{
		using simple::support::transform_arg;
		auto found = simple::support::range{
			lower_bound(begin(entity_ids), end(entity_ids), first_id, transform_arg{proj}),
		    upper_bound(begin(entity_ids), end(entity_ids), last_id,  transform_arg{proj})
		};
		assert(found.valid());

		auto found_size = simple::support::map_range(found, entity_sizes, begin(entity_ids));
		auto offset_begin = std::accumulate(begin(entity_sizes), found_size.begin(), offsets);
		auto offset_end = std::accumulate(found_size.begin(), found_size.end(), offset_begin);

		using simple::support::transform;
		transform([&](auto& components, auto offset_begin, auto offset_end)
		{
			components.erase(
				components.begin() + offset_begin,
				components.begin() + offset_end
			);
		}, _components.vectors, offset_begin, offset_end);

		entity_sizes.erase(found_size.begin(), found_size.end());
		entity_ids.erase(found.begin(), found.end());
	}

	void erase(entity_id_t first_id, entity_id_t last_id)
	{ erase(first_id, last_id, [](auto x){ return x; }); }

	void erase(entity_id_t id) { erase(id, id); }

	void erase_block(entity_id_t first_id, entity_id_t last_id)
	{ erase(first_id, last_id, [](auto x){ return unsigned(x); }); }

	void erase_block(entity_id_t id) { erase_block(id, id); }


	template <typename Component>
	decltype(auto) get_all() const noexcept
	{ return _components.template get<Component>(); }

	template <typename Component>
	auto get() const noexcept
	{
		using simple::support::make_range;
		auto components = make_range(_components.template get<Component>());
		using std::get;
		components.begin() += get<meta::find_v<Component, types>>(offsets);
		return components;
	}

	// TODO: support a subset of components, not just one or all
	template <typename Component = void>
	auto find(entity_id_t id, auto proj) noexcept
	{
		using simple::support::transform_arg;
		auto block_pair = std::equal_range(begin(entity_ids), end(entity_ids), id, transform_arg{proj});
		auto block = simple::support::range{block_pair.first, block_pair.second};

		using simple::support::transform;

		auto found_size = simple::support::map_range(block, entity_sizes, begin(entity_ids));
		auto offset_begin = std::accumulate(begin(entity_sizes), found_size.begin(), offsets);
		auto offset_end = std::accumulate(found_size.begin(), found_size.end(), offset_begin);

		constexpr auto component_index = meta::find_v<Component, types>;
		if constexpr (component_index == meta::list_size_v<types>)
		{
			auto begin = simple::support::vuple{
				transform([](auto& components)
				{
					return components.begin();
				}, _components.vectors)
			};
			return simple::support::range
			{
				begin + simple::support::vuple_from(offset_begin),
				begin + simple::support::vuple_from(offset_end)
			};
			// return transform([](auto& components, auto offset_begin, auto offset_end)
			// {
			// 	return simple::support::range
			// 	{
			// 		components.begin() + offset_begin,
			// 		components.begin() + offset_end
			// 	};
			// }, _components.vectors, offset_begin, offset_end);
		}
		else
		{
			auto& components = _components.template get<Component>();
			return simple::support::range
			{
				components.begin() + std::get<component_index>(offset_begin),
				components.begin() + std::get<component_index>(offset_end)
			};
		}
	}

	template <typename Component = void>
	auto find(entity_id_t id)
	{
		return find(id, [](entity_id_t x) { return x; });
	}

	// TODO: support a subset of components, not just one or all
	template <typename Component = void>
	auto find_block(entity_id_t id) noexcept
	{
		return find(id, [](auto x){ return unsigned(x); });
	}

	template <typename Component, typename It, size_t Index = 0>
	class component_iterator
	{
		public:
		using iterator = It;
		using difference_type = typename std::iterator_traits<iterator>::difference_type;
		using value_type = Component;
		using pointer = Component*;
		using reference = Component&;
		using iterator_category = typename std::iterator_traits<iterator>::iterator_category;

		component_iterator(It base, entities& world) :
			base(base), world(&world)
		{}

		constexpr decltype(auto) operator*() const
		{ return *(world->find<Component>(base->id).begin() + Index); }
		constexpr auto operator->() const
		{ return world->find<Component>(base->id).begin() + Index; }
		constexpr decltype(auto) operator*()
		{ return *(world->find<Component>(base->id).begin() + Index); }
		constexpr auto operator->()
		{ return world->find<Component>(base->id).begin() + Index; }

		constexpr component_iterator& operator++()
		{
			++base;
			return *this;
		}

		constexpr component_iterator operator++(int)
		{
			component_iterator ret = *this;
			++(*this);
			return ret;
		}

		constexpr component_iterator& operator+=(difference_type d)
		{
			base += d;
			return *this;
		}
		constexpr component_iterator& operator-=(difference_type d)
		{
			base -= d;
			return *this;
		}

		constexpr friend component_iterator operator+(const component_iterator& one, difference_type d)
		{
			return component_iterator(one) += d;
		}
		constexpr friend component_iterator operator+(difference_type d, const component_iterator& one)
		{
			return component_iterator(one) += d;
		}

		constexpr friend component_iterator operator-(const component_iterator& one, difference_type d)
		{
			return component_iterator(one) -= d;
		}
		constexpr friend component_iterator operator-(difference_type d, const component_iterator& one)
		{
			return component_iterator(one) -= d;
		}

		constexpr friend difference_type operator-(const component_iterator& one, const component_iterator& other)
		{
			return one.base - other.base;
		}

		constexpr friend bool operator==(const component_iterator& one, const component_iterator& other)
		{
			return one.base == other.base && one.world == other.world;
		}
		constexpr friend bool operator!=(const component_iterator& one, const component_iterator& other)
		{
			return !(one == other);
		}

		private:
		iterator base;
		entities* world;
	};

	// template <typename Component, typename It>
	// class iterator
	// {
	// 	public:
	// 	using difference_type =  entity id difference
	// 	using value_type = tuple of assignable ranges
	// 	using pointer = ???
	// 	using reference = value_type???
	// 	using iterator_category = bidirectional, could be random access if limited to a block and if block is guaranteed to be all same entity type then we can store single element size for whole block and use multiplication yay... now go do it
    //
	// 	iterator(entities& world) :
	// 		world(&world)
	// 	{}
    //
	// 	iterator(entity_id_t id, entities& world) :
	// 		base(base), world(&world)
	// 	{}
    //
	// 	private:
	// 	entities* world;
	// };

	template <typename Component, size_t Index = 0, typename EntityIt = entity_id_t*>
	auto get_component_iterator(EntityIt i)
	{
		return component_iterator<Component,EntityIt, Index>{i, *this};
	}

	std::string log_sizes() const
	{
		// TODO: entity ids, entity_sizes, offsets
		return _components.log_sizes();
	}

	private:

	std::pair<entity_id_t, std::vector<entity_id_t>::iterator> get_id(entity_id_t block_id)
	{

		using simple::support::transform_arg;
		auto block_end = std::upper_bound
		(
			begin(entity_ids),
			end(entity_ids),
			block_id,
			transform_arg{[](entity_id_t id) { return unsigned(id); }}
		);

		if(block_end == begin(entity_ids))
			return {block_id, block_end}; // block is empty, block id also is the first id

		entity_id_t last_in_block = *(block_end - 1);
		if(unsigned(last_in_block) != unsigned(block_id))
			return {block_id, block_end}; // last element is not from this block, so again it's empty

		using simple::support::next;
		auto next_id = next(last_in_block);
		if(next_id == block_id + 1) // overflew into next block
		{
			// scramble to find a free id
			auto hole = std::adjacent_find(std::reverse_iterator(block_end), entity_ids.rend(),
				[block_id](auto next_id, auto prev_id)
				{
					return next(prev_id) != next_id || unsigned(prev_id) != unsigned(block_id);
				}
			);
			assert(hole != entity_ids.rend() && *hole == block_id); // gulp
			auto next_pos = std::next(hole).base();
			return {next(*std::prev(next_pos)), next_pos};
		}
		return {next_id, block_end};
	}

	components_type _components;
	std::vector<entity_id_t> entity_ids;
	std::vector<entity_size_t> entity_sizes;
	components_type::size_type offsets;
};


#endif /* end of include guard */
